﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Xml;
using Lilium.LParse;

// по статье в журнале "Практика функционального программирования" на основе реализации на парсеке.

namespace PracticeFpOsm
{
    class Program
    {
        static void XmlReaderTest(string fileName)
        {
            var bounds = Bounds.Init();

            CultureInfo cult = CultureInfo.InvariantCulture;
            Stopwatch watch = new Stopwatch();
            using (XmlReader reader = XmlReader.Create(fileName))
            {
                watch.Start();
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element && reader.Name == "node")
                    {
                        double lat = double.Parse(reader["lat"], cult);
                        double lon = double.Parse(reader["lon"], cult);
                        bounds.maxlat = Math.Max(bounds.maxlat, lat);
                        bounds.minlat = Math.Min(bounds.minlat, lat);
                        bounds.maxlon = Math.Max(bounds.maxlon, lon);
                        bounds.minlon = Math.Min(bounds.minlon, lon);
                    }
                }
                watch.Stop();
            }
            Console.WriteLine("xml: lat={0}..{1} lon={2}..{3} t={4}", bounds.minlat, bounds.maxlat, bounds.minlon, bounds.maxlon, watch.ElapsedMilliseconds);
            Console.WriteLine((new FileInfo(fileName)).Length / watch.ElapsedMilliseconds);
        }

        static void LParse(string fileName)
        {
            var bounds = Bounds.Init();

            var floatParser = CreateFloatParser();
            var skipWhitespace = TextParsers.SkipWhile(char.IsWhiteSpace);

            var paramName =
                TextParsers.Letter.Many1()
                .SeqLeft(TextParsers.Word("=\""));

            var latlon =
                paramName.Bind(name =>
                    {
                        switch (name)
                        {
                            case "lat":
                                return floatParser.Select(lat =>
                                {
                                    bounds.maxlat = Math.Max(bounds.maxlat, lat);
                                    bounds.minlat = Math.Min(bounds.minlat, lat);
                                    return (object)null;
                                });
                            case "lon":
                                return floatParser.Select(lon =>
                                {
                                    bounds.maxlon = Math.Max(bounds.maxlon, lon);
                                    bounds.minlon = Math.Min(bounds.minlon, lon);
                                    return (object)null;
                                });
                            default:
                                return TextParsers.SkipWhile(c => c != '"');
                        }
                    })
                .SeqSkip(TextParsers.Char('"'));

            var parameters = 
                skipWhitespace
                .SeqSkip(latlon)
                .SkipMany();

            var nodeEnd =
                TextParsers.TryWord("/>")
                .Or(TextParsers.TryWord("</node>").SkipTill())
                .Pure();

            var node =
                TextParsers.Word("<node")
                .SeqSkip(parameters)
                .SeqSkip(nodeEnd);

            var tag =
                TextParsers.Char('<')
                .SeqSkip(TextParsers.SkipWhile(c => c != '>'))
                .SeqSkip(TextParsers.Char('>'));

            var nodeOrTag = node.Try().Or(tag);

            var osm = 
                nodeOrTag 
                .SeqSkip(skipWhitespace)
                .SkipMany();

            Stopwatch watch = new Stopwatch();
            watch.Start();
            osm.Parse(File.ReadAllText("../../singapore.osm"));
            watch.Stop();
            Console.WriteLine("lp:  lat={0}..{1} lon={2}..{3} t={4}", bounds.minlat, bounds.maxlat, bounds.minlon, bounds.maxlon, watch.ElapsedMilliseconds);
            Console.WriteLine((new FileInfo(fileName)).Length / watch.ElapsedMilliseconds);
        }

        static void Main(string[] args)
        {
            XmlReaderTest("../../singapore.osm");
            XmlReaderTest("../../singapore.osm");
            GC.Collect();
            LParse("../../singapore.osm");
            LParse("../../singapore.osm");
            GC.Collect();
        }

        static Parser<char, double> CreateFloatParser()
        {
            var digit = from ch in TextParsers.Range('0', '9') select (long)ch - '0';
            var decimalSeparator = TextParsers.OneOf(".,");
            var sign = from ch in TextParsers.Char('-') select -1;
            var whitesapce = TextParsers.OneOf(" \n");

            var integral = digit.Fold1Many1((a, d) => a * 10 + d);

            var fraction = from _ in decimalSeparator
                           from collected in digit.FoldMany1((a, d) => Tuple.Create(a.Item1 * 10 + d, a.Item2 + 1), Tuple.Create(0L, 0))
                           select collected.Item1 / Math.Pow(10, collected.Item2);

            var exponent = from _ in TextParsers.OneOf("eE")
                           from s in sign
                           from i in integral
                           select Math.Pow(10, i * s);

            var real = from s in sign.Optional(1)
                       from i in integral
                       from f in fraction
                       from e in exponent.Optional(1)
                       select (i + f) * s * e;

            return real;
        }

        struct Bounds
        {
            public double minlat, maxlat, minlon, maxlon;

            public static Bounds Init()
            {
                return new Bounds()
                {
                    minlat = 1000.0,
                    maxlat = -1000.0,
                    minlon = 1000.0,
                    maxlon = -1000.0,
                };
            }
        }
    }
}

/* Хаски:

import Text.ParserCombinators.Parsec
import qualified Text.ParserCombinators.Parsec.Token as Token
import Text.ParserCombinators.Parsec.Language (emptyDef)
import System.CPUTime
import System.Environment 

data Bounds = Bounds { minlat, maxlat, minlon, maxlon :: !Double } deriving (Show)

update_lat lat bnd = bnd { 
  minlat = min lat (minlat bnd), maxlat = max lat (maxlat bnd) }

update_lon lon bnd = bnd { 
  minlon = min lon (minlon bnd), maxlon = max lon (maxlon bnd) }

lexer = Token.makeTokenParser emptyDef
p_positive_float = Token.float lexer
p_float = ((char '-' >> return negate) <|> (return id)) >>= \f -> p_positive_float >>= (return . f)

latlon = do
  param <- do
    name <- many1 letter
    char '='
    char '"'
    return name
  case param of
    "lat" -> p_float >>= (updateState . update_lat)
    "lon" -> p_float >>= (updateState . update_lon)
    _ -> (many $ noneOf "\"") >> return ()
  char '"'
  return ()

p_param = do
  many1 letter
  string "=\""
  many $ noneOf "\""
  char '"'
  return ()

p_node_param = latlon

p_endnode = 
  try (string "/>") <|> manyTill anyChar (try (string "</node>"))
   
p_ws = many $ oneOf " \t\n"

p_node = do
  string "<node"
  many (p_ws >> p_node_param)
  p_endnode

p_tag = between (char '<') (char '>') (many (noneOf ">")) 

p_osm = do
  many $ (try p_node <|> p_tag) >> p_ws 
  bnd <- getState
  return $ show bnd

bnd0 = Bounds 1000.0 (-1000.0) 1000.0 (-1000.0) 

parse_osm_file fname = do
  input <- readFile fname
  case runParser p_osm bnd0 fname input of
    Right str -> putStrLn str
    Left err -> do 
      putStr "parse error at "
      print err 

main = do
  args <- getArgs
  case args of
    [] -> putStrLn "usage: bounds osmfile"
    fname:_ -> do
      t0 <- getCPUTime
      parse_osm_file fname
      t1 <- getCPUTime
      putStrLn $ show $ 1.0e-12 * (fromInteger $ t1 - t0)

*/